Current File : /var/www/e360ban/wp-content/plugins/wp-views/embedded/inc/classes/wpv-post-object-wrapper.class.php |
<?php
/**
* Views plugin object wrappers
*
* A collection of classes for encapsulating objects of the Views plugin - Views, WordPress Archives and Content
* Templates. These classes should provide easy access to commonly used properties or performed operations.
*
* The inheritance structure is following:
*
* - WPV_Post_Object_Wrapper
* .- WPV_View_Base
* . - WPV_View_Embedded
* . - WPV_WordPress_Archive_Embedded
* .- WPV_Content_Template_Embedded
*
* The *_Embedded classes are meant to be extended in full Views.
*
* @todo When we drop PHP 5.2 support, replace "self::", "WPV_View_Base::" etc. with "static::".
* Also get_postmeta_defaults() should be no longer necessary.
* For details refer to @link https://stackoverflow.com/questions/13613594/overriding-class-constants-vs-properties
*
* @since 1.8
*/
/**
* Wraps a WP_Post object.
*
* Provides basic functionality to wrap a WP_Post object and access it's properties and metadata easily.
*
* @since 1.8
*
* @property-read string $content
* @property-read string $id
* @property-read bool $is_published
* @property-read string $post_status
* @property-read string $slug
* @property-read string $title
*/
abstract class WPV_Post_Object_Wrapper {
/**
* @var int ID of the object. After constructor finishes, this should be allways set.
*/
protected $object_id = null;
/**
* @var WP_Post|null Post object or null if it was not yet fetched from the database. This should not be
* accessed directly, but through $this->post().
*/
protected $post = null;
/**
* Get the encapsulated post object.
*
* @return WP_Post
*
* @throws InvalidArgumentException When the post can't be retrieved.
*/
abstract protected function &post();
/**
* Return array of default post meta.
*
* This is a workaround about not being able to reliably use self:: because of PHP 5.2.
*
* @return array Default post meta.
*/
abstract protected function get_postmeta_defaults();
/* ************************************************************************* *\
Static methods
\* ************************************************************************* */
/**
* Check if a name is already in use for given post type.
*
* Returns true if the name is used either as a post title or a post slug.
*
* @param string $name Name to check.
* @param string $post_type Post type slug.
* @param int $except_id Post ID that should be excluded from checking.
* @param array &$collision_data (since 1.10) If there is a name collision, this will be set to an array:
* - id: ID of the other post
* - colliding_field: Where has the collision with $name happened: 'post_title', 'post_name' or 'both'
* - post_title: Title of the other post
*
* @return bool True if name is already used elsewhere, false otherwise.
*
* @since 2.2.2 Queries can be case insensitibe, depending on the database collation.
* Because of that, we need to lowercase when checking the collision cause.
*/
public static function is_name_used_base( $name, $post_type, $except_id = 0, &$collision_data = null ) {
global $wpdb;
$query = $wpdb->prepare(
"SELECT post_title, post_name, ID
FROM {$wpdb->posts}
WHERE ( post_title = %s OR post_name = %s )
AND post_type = %s
AND ID != %d
LIMIT 1",
$name,
$name,
$post_type,
$except_id
);
$existing_post = $wpdb->get_row( $query );
$collision_exists = ( null != $existing_post );
if( $collision_exists ) {
$title_collides = ( strtolower( $name ) == strtolower( $existing_post->post_title ) );
$slug_collides = ( sanitize_title( $name ) == strtolower( $existing_post->post_name ) );
if( $title_collides && $slug_collides ) {
$colliding_field = 'both';
} else if( $title_collides ) {
$colliding_field = 'post_title';
} else if( $slug_collides ) {
$colliding_field = 'post_name';
}
/** @noinspection PhpUndefinedVariableInspection */
$collision_data = array(
'id' => $existing_post->ID,
'colliding_field' => $colliding_field,
'post_title' => $existing_post->post_title
);
}
return $collision_exists;
}
/**
* Generate an unique title for an object based on a candidate value.
*
* Note that this might be quite a database-intensive operation under certain circumstances.
*
* @param string $post_type Post type slug.
* @param string $title_candidate Non-blank (e.g. not only whitespace) title candidate.
* @param int $except_id CT id that should be excluded from the uniqueness check.
* @return null|string An unique title or null if the input was invalid.
*
* @since 1.12
*/
public static function get_unique_title_base( $post_type, $title_candidate, $except_id = 0 ) {
$title_candidate = trim( $title_candidate );
if( empty( $title_candidate ) ) {
return null;
}
// If the title is already unique, we're done.
if( ! WPV_Post_Object_Wrapper::is_name_used_base( $title_candidate, $post_type, $except_id ) ) {
return $title_candidate;
}
// If current title has a number at it's end, we'll use it and start incrementing it. If not,
// we will just add a number as a suffix.
$title_parts = explode( ' ', trim( $title_candidate ) );
// there will allways be at least one part
$last_title_part = $title_parts[ count( $title_parts ) - 1 ];
if( is_numeric( $last_title_part ) ) {
$numeric_suffix = $last_title_part + 1;
$title_base = implode( ' ', array_slice( $title_parts, 0, -1 ) );
} else {
$numeric_suffix = 2;
$title_base = $title_candidate;
}
// Keep incrementing the suffix until an unique title is found.
do {
$title_candidate = "$title_base $numeric_suffix";
++$numeric_suffix;
} while( WPV_Post_Object_Wrapper::is_name_used_base( $title_candidate, $post_type, $except_id ) );
return $title_candidate;
}
/* ************************************************************************* *\
Getter and setter
\* ************************************************************************* */
/**
* Dynamic attribute getter.
*
* Returns the value of custom getter with the name _get_{$attribute_name}(), if it exists. Otherwise
* null is returned.
*
* @param string $attribute_name Name of the attribute.
*
* @return mixed Value of the attribute or null if it doesn't exist.
*/
public function __get( $attribute_name ) {
// Custom getter
$method_name = '_get_' . $attribute_name;
if( method_exists( $this, $method_name ) ) {
return $this->$method_name();
}
// Everything has failed
return null;
}
/**
* Dynamic attribute setter.
*
* The attribute is set in following way:
* 1. By a custom setter with the name in the format _set_{$attribute_name}, if such exists.
* 2. As a postmeta value, if the key is defined in self::$postmeta_defaults.
*
* If the attribute cannot be set, an InvalidArgumentException is thrown. Note that this is well-defined behaviour.
*
* @param string $attribute_name Name of the attribute.
* @param mixed $value Value to be set.
*
* @throws InvalidArgumentException if the attribute value cannot be set.
*/
public function __set( $attribute_name, $value ) {
// Custom setter
$method_name = '_set_' . $attribute_name;
if( method_exists( $this, $method_name ) ) {
$this->$method_name( $value );
return;
}
// If the key is defined in postmeta defaults, set the value as postmeta
/*if( in_array( $attribute_name, $this->get_postmeta_defaults() ) ) {
update_post_meta( $this->object_id, $attribute_name, $value );
$this->maybe_after_update_action();
}*/
// The value can't be set.
throw new InvalidArgumentException( "Invalid attribute name: $attribute_name" );
}
/* ************************************************************************* *\
Post updating, postmeta access
\* ************************************************************************* */
/**
* @var bool If true, after update action will not be executed until
* resume_after_update_actions().
*
* @since 1.9
*/
private $is_after_update_action_deferred = false;
/**
* @var bool Indicate whether after update action should be called on
* resume_after_update_actions().
*
* @since 1.9
*/
private $is_after_update_action_needed = false;
/**
* Stop executing after update action automatically after each
* property change.
*
* By calling this method you are becoming responsible for either
* invoking the after update action manually or returning to the
* automatic mode via resume_after_update_actions().
*
* @since 1.9
*/
public function defer_after_update_actions() {
$this->is_after_update_action_deferred = true;
}
/**
* Run the after update action manually.
*
* @since 1.9
*/
public function after_update_action() {
do_action( 'wpv_action_wpv_save_item', $this->id );
$this->is_after_update_action_needed = false;
}
/**
* Run the after update action or indicate it needs to be executed,
* depending on the current mode (automatic or deferred).
*
* @since 1.9
*/
public function maybe_after_update_action() {
if( $this->is_after_update_action_deferred ) {
$this->is_after_update_action_needed = true;
} else {
$this->after_update_action();
}
}
/**
* Return to the automatic mode of running after update action and run
* the action if it is needed.
*
* @since 1.9
*/
public function resume_after_update_actions() {
$this->is_after_update_action_deferred = false;
if( $this->is_after_update_action_needed ) {
$this->after_update_action();
}
}
/**
* Update View's post record in the database.
*
* It works as wp_update_post() with only few differences:
*
* - The ID argument is not mandatory.
* - If an ID is provided, it must match ID of this View.
* - After updating, the privately stored WP_Post object is discarded.
*
* @param array $args Array of arguments for wp_update_post();
*
* @return int|WP_Error ID of the updated post or a WP_Error object.
*/
public function update_post( $args ) {
if( !is_array( $args ) ) {
throw new InvalidArgumentException( 'args is not an array.' );
}
if( in_array( 'ID', $args ) && ( $args['ID'] != $this->object_id ) ) {
throw new InvalidArgumentException( 'Invalid ID given as an argument' );
}
// Make sure that wp_update_post gets the ID it needs.
$args['ID'] = $this->id;
$updated = wp_update_post( $args, true );
// Force to reload post from cache
$this->post = null;
$this->maybe_after_update_action();
return $updated;
}
/**
* Get postmeta from this post.
*
* Method for getting single-value postmeta. Return value is determined in following way:
*
* 1. Postmeta value, if it exists.
* 2. Default postmeta value in self::postmeta_defaults, if the attribute name is defined there.
* 3. null
*
* @param string $key Meta key.
* @return mixed
*
* @since 1.9
*/
public function get_postmeta( $key ) {
// Postmeta
$meta_value = get_post_meta( $this->object_id, $key, true );
if( '' !== $meta_value ) {
/* get_post_meta() returns an empty string if no postmeta with given key is present.
* So, now we know for sure it is. */
return $meta_value;
}
/* Now we know that postmeta either doesn't exist or it is an empty string. Which one is it?
* Note: No additional query needed here, everything is cached in WP core. */
if( metadata_exists( 'post', $this->object_id, $key ) ) {
// It was indeed an empty string.
return '';
}
// Look for a default value
$postmeta_defaults = $this->get_postmeta_defaults();
if( array_key_exists( $key, $postmeta_defaults ) ) {
return $postmeta_defaults[ $key ];
}
// Everything else has failed
return null;
}
/**
* Update postmeta of this post.
*
* Shortcut method for setting single-value postmeta.
*
* @param string $key Meta key.
* @param string $value New meta value.
* @return bool Same as update_post_meta(), except that it returns true instead of meta_id.
*/
public function update_postmeta( $key, $value ) {
$ret = update_post_meta( $this->object_id, $key, $value );
/**
* Action for when updating the post meta.
*
* @since 2.7.0
*/
do_action( "wpv_updated_{$key}_meta", $this->object_id, $key, $value );
$this->maybe_after_update_action();
if( is_numeric( $ret ) ) {
// $ret is new meta_id, but we will just indicate success. There should be no
// reason to need meta_id.
return true;
} else {
return $ret;
}
}
/* ************************************************************************* *\
Update transactions
\* ************************************************************************* */
/**
* This method provides a very generic mechanism for updating multiple properties
* in a transaction - that means, either all of them get updated or none does.
*
* Of course, support for individual properties is necessary: The class has to
* contain validation methods _validate_{$property_name} that will accept
* the candidate value and *throw an exception* if the validation fails. It's
* return value is ignored here.
*
* If the property has no validation method, it is assumed that any value will
* be accepted.
*
* To be more exact, the general rule is that if a value passes through validation
* (or if validation method doesn't exist), it should also pass through the setter
* without any exceptions being thrown.
*
* Failure to follow this rule might cause "partial success" of this method, which
* is exactly what it tries to avoid.
*
* @param array $data Array with update data, property values indexed by their names.
* array( 'property_one' => $any_value, 'property_two' => ... )
* @param bool $break_on_error Define whether to stop validation on first error or continue
* validating other properties even after it's clear that the transaction will fail.
* Default is true.
*
* @return array(
* Results of the action.
* @type bool $success True if *all* properties have been successfully updated.
* @type array $error_messages Error messages to display to the user (if there are any),
* indexed by names of properties that caused them. Each element is an array(
* @type string $message Text of the message.
* @type int $code Message code (zero means not set).
* ).
* @type bool $partial If this is true, it means that some properties have been
* updated and some not. This can be true only if $success is false.
* @type array $updated_properties This is set only if $partial is true, and in such case
* it contains names of properties that *have* been successfully updated.
* @type string $first_error_message For the convenience, if there are any error messages,
* this will contain the text of the first one.
* )
*
* @since 1.9
*/
public function update_transaction( $data, $break_on_error = true ) {
if( !is_array( $data ) ) {
// ...nothing to do here!
return array( 'success' => false, 'partial' => false, 'error_messages' => array() );
}
// Try to validate all properties without updating first.
$can_update_everything = true;
$error_messages = array();
$first_error_message = null;
foreach( $data as $property_name => $value ) {
// Fail if property doesn't have a setter
$setter_method_name = '_set_' . $property_name;
if( !method_exists( $this, $setter_method_name ) ) {
$can_update_everything = false;
continue;
}
$validate_method_name = '_validate_' . $property_name;
if( method_exists( $this, $validate_method_name ) ) {
// we have a validation method for this property
try {
$this->$validate_method_name( $value );
} catch(WPV_RuntimeExceptionWithMessage $e) {
// Validation has failed with a message.
$can_update_everything = false;
$error_messages[ $property_name ] = array(
'message' => $e->getUserMessage(),
'code' => $e->getCode()
);
$first_error_message = (null == $first_error_message) ? $e->getUserMessage() : $first_error_message;
if( $break_on_error ) {
// We can stop trying now.
break;
} else {
continue;
}
} catch(Exception $e) {
// Validation has failed without a message. So we will keep trying
// to validate other properties and perhaps some other will give us
// something to display.
$can_update_everything = false;
continue;
}
} else {
// no validation method available, we assume everything is ok
}
}
// Abort the transaction if anything was not validated.
if( !$can_update_everything ) {
$result = array( 'success' => false, 'partial' => false, 'error_messages' => $error_messages );
if( null != $first_error_message ) {
$result['first_error_message'] = $first_error_message;
}
return $result;
}
// Commit the transaction
$was_after_update_action_deferred = $this->is_after_update_action_deferred;
$this->defer_after_update_actions();
$did_update_everything = true;
$updated_properties = array();
foreach( $data as $property_name => $value ) {
$setter_method_name = '_set_' . $property_name;
// The setter still might throw something. It shouldn't, but we will not count on that.
try {
$this->$setter_method_name( $value );
// Update performed
$updated_properties[] = $property_name;
} catch(WPV_RuntimeExceptionWithMessage $e) {
$did_update_everything = false;
$error_messages[ $property_name ] = array(
'message' => $e->getUserMessage(),
'code' => $e->getCode()
);
$first_error_message = (null == $first_error_message) ? $e->getUserMessage() : $first_error_message;
continue;
} catch(Exception $e) {
$did_update_everything = false;
continue;
}
}
if( !$was_after_update_action_deferred ) {
$this->resume_after_update_actions();
}
// todo handle the situation when first property update fails
$ret = array(
'success' => $did_update_everything,
'partial' => !$did_update_everything,
'error_messages' => $error_messages
);
if( !$did_update_everything ) {
$ret['updated_properties'] = $updated_properties;
}
if( null != $first_error_message ) {
$ret['first_error_message'] = $first_error_message;
}
return $ret;
}
/* ************************************************************************* *\
Custom getters and setters
\* ************************************************************************* */
/**
* @return string The post status. @see http://codex.wordpress.org/Function_Reference/get_post_status
*/
protected function _get_post_status() {
return $this->post()->post_status;
}
/**
* True, if the post is published. False otherwise.
* @return bool
*/
protected function _get_is_published() {
return ( 'publish' == $this->post_status );
}
/**
* True, if the post is trashed. False otherwise.
* @return bool
*/
protected function _get_is_trashed() {
return ( 'trash' == $this->post_status );
}
/**
* Get post title.
* @return string
*/
protected function _get_title() {
return sanitize_text_field( $this->post()->post_title );
}
/**
* Post slug.
* @return string
*/
protected function _get_slug() {
return sanitize_text_field( $this->post()->post_name );
}
/**
* Counterpart of the _set_slug_safe() in WPV_Content_Template.
*
* @return string
* @since 1.9
*/
protected function _get_slug_safe() {
return $this->slug;
}
/**
* Post ID.
* @return int
*/
protected function _get_id() {
return (int) $this->object_id;
}
/**
* Post content.
* @return string
*/
protected function _get_content() {
return $this->post()->post_content;
}
/**
* Returns the WP_POST object
*
* @return WP_Post
*/
public function get_post() {
return $this->post;
}
}